/*-------------------------------------------------------------------------
 *
 * functioncmds.c
 *
 *	  CREATE 和 DROP FUNCTION 命令以及 CREATE 和 DROP
 *	  CAST 命令的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/functioncmds.c
 *
 * DESCRIPTION
 *	  这些例程提取解析树并挑选合适的参数/标志，并将结果传递给相应的 "FooDefine" 例程（在 src/catalog 中），执行实际的目录修改操作。  这些例程还验证用户执行命令的权限。
 *
 * NOTES
 *	  这些事情必须按以下顺序定义和提交：
 *		"create function":
 *				input/output, recv/send 过程
 *		"create type":
 *				type
 *		"create operator":
 *				operators
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "commands/alter.h"
#include "commands/defrem.h"
#include "commands/extension.h"
#include "commands/proclang.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/analyze.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
#include "parser/parse_func.h"
#include "parser/parse_type.h"
#include "pgstat.h"
#include "tcop/pquery.h"
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/typcache.h"

/*
 * 检查 CREATE FUNCTION 语句的 RETURNS 子句
 * 并以 *prorettype_p 和 *returnsSet 的形式返回关于它的信息。
 *
 * 这比一般的类型名称查找要复杂，因为我们想要
 * 允许使用 shell 类型，甚至在指定的返回类型
 * 尚不存在时创建它。（没有这个，就无法定义新类型的 I/O procs。）
 * 但 SQL 函数创建无法适应，因此如果目标语言是 SQL，则报错。
 * （我们在这里这样做，而不是在 SQL 函数验证器中，以便不产生 NOTICE 然后再产生 ERROR 的同一条件。）
 */
static void fc_compute_return_type(TypeName *fc_returnType, Oid fc_languageOid,
					Oid *fc_prorettype_p, bool *fc_returnsSet_p)
{
	Oid			fc_rettype;
	Type		fc_typtup;
	AclResult	fc_aclresult;

	fc_typtup = LookupTypeName(NULL, fc_returnType, NULL, false);

	if (fc_typtup)
	{
		if (!((Form_pg_type) GETSTRUCT(fc_typtup))->typisdefined)
		{
			if (fc_languageOid == SQLlanguageId)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("SQL function cannot return shell type %s",
								TypeNameToString(fc_returnType))));
			else
				ereport(NOTICE,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("return type %s is only a shell",
								TypeNameToString(fc_returnType))));
		}
		fc_rettype = typeTypeId(fc_typtup);
		ReleaseSysCache(fc_typtup);
	}
	else
	{
		char	   *fc_typnam = TypeNameToString(fc_returnType);
		Oid			fc_namespaceId;
		AclResult	fc_aclresult;
		char	   *fc_typname;
		ObjectAddress fc_address;

		/*
		 * 只有 C 语言编码的函数才能是 I/O 函数。我们在这里强制执行
		 * 这个限制，主要是为了防止由于用户自定义函数定义中的简单拼写错误
		 * 而在目录中留下 shell 类型的杂物。
		 */
		if (fc_languageOid != INTERNALlanguageId &&
			fc_languageOid != ClanguageId)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("type \"%s\" does not exist", fc_typnam)));

		/* 如果有 typmod 装饰则拒绝 */
		if (fc_returnType->typmods != NIL)
			ereport(ERROR,
					(errcode(ERRCODE_SYNTAX_ERROR),
					 errmsg("type modifier cannot be specified for shell type \"%s\"",
							fc_typnam)));

		/* 否则，继续创建一个 shell 类型 */
		ereport(NOTICE,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("type \"%s\" is not yet defined", fc_typnam),
				 errdetail("Creating a shell type definition.")));
		fc_namespaceId = QualifiedNameGetCreationNamespace(fc_returnType->names,
														&fc_typname);
		fc_aclresult = pg_namespace_aclcheck(fc_namespaceId, GetUserId(),
										  ACL_CREATE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
						   get_namespace_name(fc_namespaceId));
		fc_address = TypeShellMake(fc_typname, fc_namespaceId, GetUserId());
		fc_rettype = fc_address.objectId;
		Assert(OidIsValid(fc_rettype));
	}

	fc_aclresult = pg_type_aclcheck(fc_rettype, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_rettype);

	*fc_prorettype_p = fc_rettype;
	*fc_returnsSet_p = fc_returnType->setof;
}

/*
 * 解释 CREATE FUNCTION、CREATE PROCEDURE 或 CREATE AGGREGATE 语句的函数参数列表。
 *
 * 输入参数：
 * parameters: FunctionParameter 结构体列表
 * languageOid: 函数语言的 OID（如果是 CREATE AGGREGATE，则为 InvalidOid）
 * objtype: 确定要创建的对象类型
 *
 * 结果存储到输出参数中。parameterTypes 必须始终创建，
 * 但其他数组/列表在不需要时可以为 NULL 指针。
 * variadicArgType 如果有 VARIADIC 参数（只能有一个）则设置为可变参数数组类型；
 * 如果没有，则设置为 InvalidOid。
 * 如果没有 OUT 参数，requiredResultType 设置为 InvalidOid，
 * 否则设置为隐含结果类型的 OID。
 */
void interpret_function_parameter_list(ParseState *fc_pstate,
								  List *fc_parameters,
								  Oid fc_languageOid,
								  ObjectType fc_objtype,
								  oidvector **fc_parameterTypes,
								  List **fc_parameterTypes_list,
								  ArrayType **fc_allParameterTypes,
								  ArrayType **fc_parameterModes,
								  ArrayType **fc_parameterNames,
								  List **fc_inParameterNames_list,
								  List **fc_parameterDefaults,
								  Oid *fc_variadicArgType,
								  Oid *fc_requiredResultType)
{
	int			fc_parameterCount = list_length(fc_parameters);
	Oid		   *fc_inTypes;
	int			fc_inCount = 0;
	Datum	   *fc_allTypes;
	Datum	   *fc_paramModes;
	Datum	   *fc_paramNames;
	int			fc_outCount = 0;
	int			fc_varCount = 0;
	bool		fc_have_names = false;
	bool		fc_have_defaults = false;
	ListCell   *fc_x;
	int			fc_i;

	*fc_variadicArgType = InvalidOid;	/* 默认结果 */
	*fc_requiredResultType = InvalidOid;	/* 默认结果 */

	fc_inTypes = (Oid *) palloc(fc_parameterCount * sizeof(Oid));
	fc_allTypes = (Datum *) palloc(fc_parameterCount * sizeof(Datum));
	fc_paramModes = (Datum *) palloc(fc_parameterCount * sizeof(Datum));
	fc_paramNames = (Datum *) palloc0(fc_parameterCount * sizeof(Datum));
	*fc_parameterDefaults = NIL;

	/* 扫描列表并提取数据到工作数组 */
	fc_i = 0;
	foreach(fc_x, fc_parameters)
	{
		FunctionParameter *fc_fp = (FunctionParameter *) lfirst(fc_x);
		TypeName   *fc_t = fc_fp->argType;
		FunctionParameterMode fc_fpmode = fc_fp->mode;
		bool		fc_isinput = false;
		Oid			fc_toid;
		Type		fc_typtup;
		AclResult	fc_aclresult;

		/* 对于我们的目的来说，默认模式规范与 IN 相同 */
		if (fc_fpmode == FUNC_PARAM_DEFAULT)
			fc_fpmode = FUNC_PARAM_IN;

		fc_typtup = LookupTypeName(NULL, fc_t, NULL, false);
		if (fc_typtup)
		{
			if (!((Form_pg_type) GETSTRUCT(fc_typtup))->typisdefined)
			{
				/* 如上所述，如果语言是 SQL，则硬性报错 */
				if (fc_languageOid == SQLlanguageId)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
							 errmsg("SQL function cannot accept shell type %s",
									TypeNameToString(fc_t))));
				/* 我们也不允许在 shell 类型上创建聚合 */
				else if (fc_objtype == OBJECT_AGGREGATE)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
							 errmsg("aggregate cannot accept shell type %s",
									TypeNameToString(fc_t))));
				else
					ereport(NOTICE,
							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
							 errmsg("argument type %s is only a shell",
									TypeNameToString(fc_t))));
			}
			fc_toid = typeTypeId(fc_typtup);
			ReleaseSysCache(fc_typtup);
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("type %s does not exist",
							TypeNameToString(fc_t))));
			fc_toid = InvalidOid;	/* 保持编译器安静 */
		}

		fc_aclresult = pg_type_aclcheck(fc_toid, GetUserId(), ACL_USAGE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error_type(fc_aclresult, fc_toid);

		if (fc_t->setof)
		{
			if (fc_objtype == OBJECT_AGGREGATE)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("aggregates cannot accept set arguments")));
			else if (fc_objtype == OBJECT_PROCEDURE)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("procedures cannot accept set arguments")));
			else
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("functions cannot accept set arguments")));
		}

		/* 处理输入参数 */
		if (fc_fpmode != FUNC_PARAM_OUT && fc_fpmode != FUNC_PARAM_TABLE)
		{
			/* 其他输入参数不能跟随 VARIADIC 参数 */
			if (fc_varCount > 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("VARIADIC parameter must be the last input parameter")));
			fc_inTypes[fc_inCount++] = fc_toid;
			fc_isinput = true;
			if (fc_parameterTypes_list)
				*fc_parameterTypes_list = lappend_oid(*fc_parameterTypes_list, fc_toid);
		}

		/* 处理输出参数 */
		if (fc_fpmode != FUNC_PARAM_IN && fc_fpmode != FUNC_PARAM_VARIADIC)
		{
			if (fc_objtype == OBJECT_PROCEDURE)
			{
				/*
				 * 我们仅对过程不允许在 VARIADIC 后面使用 OUT。虽然
				 * 这种情况在普通函数调用中不会造成混淆，但在 CALL 语句中会造成混淆。
				 */
				if (fc_varCount > 0)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
							 errmsg("VARIADIC parameter must be the last parameter")));
				/* 带有输出参数的过程始终返回 RECORD */
				*fc_requiredResultType = RECORDOID;
			}
			else if (fc_outCount == 0) /* 保存第一个输出参数的类型 */
				*fc_requiredResultType = fc_toid;
			fc_outCount++;
		}

		if (fc_fpmode == FUNC_PARAM_VARIADIC)
		{
			*fc_variadicArgType = fc_toid;
			fc_varCount++;
			/* 验证可变参数类型 */
			switch (fc_toid)
			{
				case ANYARRAYOID:
				case ANYCOMPATIBLEARRAYOID:
				case ANYOID:
					/* 可以 */
					break;
				default:
					if (!OidIsValid(get_element_type(fc_toid)))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
								 errmsg("VARIADIC parameter must be an array")));
					break;
			}
		}

		fc_allTypes[fc_i] = ObjectIdGetDatum(fc_toid);

		fc_paramModes[fc_i] = CharGetDatum(fc_fpmode);

		if (fc_fp->name && fc_fp->name[0])
		{
			ListCell   *fc_px;

			/*
			 * 从 Postgres 9.0 开始，我们禁止对两个输入或两个输出函数参数使用相同的名称。
			 * 根据函数的语言，冲突的输入和输出名称可能也会出现问题，但我们将此留给 PL 进行投诉。
			 */
			foreach(fc_px, fc_parameters)
			{
				FunctionParameter *fc_prevfp = (FunctionParameter *) lfirst(fc_px);
				FunctionParameterMode fc_prevfpmode;

				if (fc_prevfp == fc_fp)
					break;
				/* 如上所述，默认模式是 IN */
				fc_prevfpmode = fc_prevfp->mode;
				if (fc_prevfpmode == FUNC_PARAM_DEFAULT)
					fc_prevfpmode = FUNC_PARAM_IN;
				/* 纯 IN 不与纯 OUT 冲突 */
				if ((fc_fpmode == FUNC_PARAM_IN ||
					 fc_fpmode == FUNC_PARAM_VARIADIC) &&
					(fc_prevfpmode == FUNC_PARAM_OUT ||
					 fc_prevfpmode == FUNC_PARAM_TABLE))
					continue;
				if ((fc_prevfpmode == FUNC_PARAM_IN ||
					 fc_prevfpmode == FUNC_PARAM_VARIADIC) &&
					(fc_fpmode == FUNC_PARAM_OUT ||
					 fc_fpmode == FUNC_PARAM_TABLE))
					continue;
				if (fc_prevfp->name && fc_prevfp->name[0] &&
					strcmp(fc_prevfp->name, fc_fp->name) == 0)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
							 errmsg("parameter name \"%s\" used more than once",
									fc_fp->name)));
			}

			fc_paramNames[fc_i] = CStringGetTextDatum(fc_fp->name);
			fc_have_names = true;
		}

		if (fc_inParameterNames_list)
			*fc_inParameterNames_list = lappend(*fc_inParameterNames_list, makeString(fc_fp->name ? fc_fp->name : pstrdup("")));

		if (fc_fp->defexpr)
		{
			Node	   *fc_def;

			if (!fc_isinput)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("only input parameters can have default values")));

			fc_def = transformExpr(fc_pstate, fc_fp->defexpr,
								EXPR_KIND_FUNCTION_DEFAULT);
			fc_def = coerce_to_specific_type(fc_pstate, fc_def, fc_toid, "DEFAULT");
			assign_expr_collations(fc_pstate, fc_def);

			/*
			 * 确保没有变量被引用（这可能是死代码，因为 add_missing_from 已经历史）。
			 */
			if (list_length(fc_pstate->p_rtable) != 0 ||
				contain_var_clause(fc_def))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("cannot use table references in parameter default value")));

			/*
			 * transformExpr() 应该已经根据默认表达式的 EXPR_KIND_ 拒绝了子查询、
			 * 聚合和窗口函数。
			 *
			 * 它也不能返回集合——但是 coerce_to_specific_type 
			 * 已经为我们检查了这一点。
			 *
			 * 注意：这些限制的重点是确保一个表面上没有子计划、聚合等的
			 * 表达式在插入函数默认参数后不会突然出现它们。
			 */

			*fc_parameterDefaults = lappend(*fc_parameterDefaults, fc_def);
			fc_have_defaults = true;
		}
		else
		{
			if (fc_isinput && fc_have_defaults)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("input parameters after one with a default value must also have defaults")));

			/*
			 * 对于过程，我们也不能允许在有默认值的参数之后出现 OUT 参数，
			 * 因为在 CALL 语句中会产生同样的困惑。
			 */
			if (fc_objtype == OBJECT_PROCEDURE && fc_have_defaults)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("procedure OUT parameters cannot appear after one with a default value")));
		}

		fc_i++;
	}

	/* 现在根据需要构造适当的输出 */
	*fc_parameterTypes = buildoidvector(fc_inTypes, fc_inCount);

	if (fc_outCount > 0 || fc_varCount > 0)
	{
		*fc_allParameterTypes = construct_array(fc_allTypes, fc_parameterCount, OIDOID,
											 sizeof(Oid), true, TYPALIGN_INT);
		*fc_parameterModes = construct_array(fc_paramModes, fc_parameterCount, CHAROID,
										  1, true, TYPALIGN_CHAR);
		if (fc_outCount > 1)
			*fc_requiredResultType = RECORDOID;
		/* 否则我们在上面正确设置 requiredResultType */
	}
	else
	{
		*fc_allParameterTypes = NULL;
		*fc_parameterModes = NULL;
	}

	if (fc_have_names)
	{
		for (fc_i = 0; fc_i < fc_parameterCount; fc_i++)
		{
			if (fc_paramNames[fc_i] == PointerGetDatum(NULL))
				fc_paramNames[fc_i] = CStringGetTextDatum("");
		}
		*fc_parameterNames = construct_array(fc_paramNames, fc_parameterCount, TEXTOID,
										  -1, false, TYPALIGN_INT);
	}
	else
		*fc_parameterNames = NULL;
}


/*
 * 识别可以传递给 CREATE FUNCTION 和 ALTER FUNCTION 的选项之一，
 * 并通过其中一个输出参数返回。如果传入的选项被识别，则返回 true。
 * 如果我们要赋值的输出参数指向非 NULL，抛出重复子句错误。
 * （我们不尝试检测重复的 SET 参数——如果你是冗余的，则最后一个生效。）
 */
static bool fc_compute_common_attribute(ParseState *fc_pstate,
						 bool fc_is_procedure,
						 DefElem *fc_defel,
						 DefElem **fc_volatility_item,
						 DefElem **fc_strict_item,
						 DefElem **fc_security_item,
						 DefElem **fc_leakproof_item,
						 List **fc_set_items,
						 DefElem **fc_cost_item,
						 DefElem **fc_rows_item,
						 DefElem **fc_support_item,
						 DefElem **fc_parallel_item)
{
	if (strcmp(fc_defel->defname, "volatility") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_volatility_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_volatility_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "strict") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_strict_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_strict_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "security") == 0)
	{
		if (*fc_security_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_security_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "leakproof") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_leakproof_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_leakproof_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "set") == 0)
	{
		*fc_set_items = lappend(*fc_set_items, fc_defel->arg);
	}
	else if (strcmp(fc_defel->defname, "cost") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_cost_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_cost_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "rows") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_rows_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_rows_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "support") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_support_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_support_item = fc_defel;
	}
	else if (strcmp(fc_defel->defname, "parallel") == 0)
	{
		if (fc_is_procedure)
			goto procedure_error;
		if (*fc_parallel_item)
			errorConflictingDefElem(fc_defel, fc_pstate);

		*fc_parallel_item = fc_defel;
	}
	else
		return false;

	/* 识别了一个选项 */
	return true;

procedure_error:
	ereport(ERROR,
			(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
			 errmsg("invalid attribute in procedure definition"),
			 parser_errposition(fc_pstate, fc_defel->location)));
	return false;
}

static char fc_interpret_func_volatility(DefElem *fc_defel)
{
	char	   *fc_str = strVal(fc_defel->arg);

	if (strcmp(fc_str, "immutable") == 0)
		return PROVOLATILE_IMMUTABLE;
	else if (strcmp(fc_str, "stable") == 0)
		return PROVOLATILE_STABLE;
	else if (strcmp(fc_str, "volatile") == 0)
		return PROVOLATILE_VOLATILE;
	else
	{
		elog(ERROR, "invalid volatility \"%s\"", fc_str);
		return 0;				/* 保持编译器安静 */
	}
}

static char fc_interpret_func_parallel(DefElem *fc_defel)
{
	char	   *fc_str = strVal(fc_defel->arg);

	if (strcmp(fc_str, "safe") == 0)
		return PROPARALLEL_SAFE;
	else if (strcmp(fc_str, "unsafe") == 0)
		return PROPARALLEL_UNSAFE;
	else if (strcmp(fc_str, "restricted") == 0)
		return PROPARALLEL_RESTRICTED;
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("parameter \"parallel\" must be SAFE, RESTRICTED, or UNSAFE")));
		return PROPARALLEL_UNSAFE;	/* 保持编译器安静 */
	}
}

/*
 * 根据一系列 VariableSetStmt 项目更新 proconfig 值。
 *
 * 输入和结果可以为 NULL，以表示空条目。
 */
static ArrayType * fc_update_proconfig_value(ArrayType *fc_a, List *fc_set_items)
{
	ListCell   *fc_l;

	foreach(fc_l, fc_set_items)
	{
		VariableSetStmt *fc_sstmt = lfirst_node(VariableSetStmt, fc_l);

		if (fc_sstmt->kind == VAR_RESET_ALL)
			fc_a = NULL;
		else
		{
			char	   *fc_valuestr = ExtractSetVariableArgs(fc_sstmt);

			if (fc_valuestr)
				fc_a = GUCArrayAdd(fc_a, fc_sstmt->name, fc_valuestr);
			else				/* RESET */
				fc_a = GUCArrayDelete(fc_a, fc_sstmt->name);
		}
	}

	return fc_a;
}

static Oid fc_interpret_func_support(DefElem *fc_defel)
{
	List	   *fc_procName = defGetQualifiedName(fc_defel);
	Oid			fc_procOid;
	Oid			fc_argList[1];

	/*
	 * 支持函数始终接受一个 INTERNAL 参数并返回 INTERNAL。
	 */
	fc_argList[0] = INTERNALOID;

	fc_procOid = LookupFuncName(fc_procName, 1, fc_argList, true);
	if (!OidIsValid(fc_procOid))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_FUNCTION),
				 errmsg("function %s does not exist",
						func_signature_string(fc_procName, 1, NIL, fc_argList))));

	if (get_func_rettype(fc_procOid) != INTERNALOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("support function %s must return type %s",
						NameListToString(fc_procName), "internal")));

	/*
	 * 总有一天我们可能希望在这里进行 ACL 检查；但目前为止，我们要求
	 * 您是超级用户才能指定支持函数，因此对支持函数的权限是无关紧要的。
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to specify a support function")));

	return fc_procOid;
}


/*
 * 将 gram.y 中组装的选项列表剖析为函数属性。
 */
static void fc_compute_function_attributes(ParseState *fc_pstate,
							bool fc_is_procedure,
							List *fc_options,
							List **fc_as,
							char **fc_language,
							Node **fc_transform,
							bool *fc_windowfunc_p,
							char *fc_volatility_p,
							bool *fc_strict_p,
							bool *fc_security_definer,
							bool *fc_leakproof_p,
							ArrayType **fc_proconfig,
							float4 *fc_procost,
							float4 *fc_prorows,
							Oid *fc_prosupport,
							char *fc_parallel_p)
{
	ListCell   *fc_option;
	DefElem    *fc_as_item = NULL;
	DefElem    *fc_language_item = NULL;
	DefElem    *fc_transform_item = NULL;
	DefElem    *fc_windowfunc_item = NULL;
	DefElem    *fc_volatility_item = NULL;
	DefElem    *fc_strict_item = NULL;
	DefElem    *fc_security_item = NULL;
	DefElem    *fc_leakproof_item = NULL;
	List	   *fc_set_items = NIL;
	DefElem    *fc_cost_item = NULL;
	DefElem    *fc_rows_item = NULL;
	DefElem    *fc_support_item = NULL;
	DefElem    *fc_parallel_item = NULL;

	foreach(fc_option, fc_options)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_option);

		if (strcmp(fc_defel->defname, "as") == 0)
		{
			if (fc_as_item)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_as_item = fc_defel;
		}
		else if (strcmp(fc_defel->defname, "language") == 0)
		{
			if (fc_language_item)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_language_item = fc_defel;
		}
		else if (strcmp(fc_defel->defname, "transform") == 0)
		{
			if (fc_transform_item)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_transform_item = fc_defel;
		}
		else if (strcmp(fc_defel->defname, "window") == 0)
		{
			if (fc_windowfunc_item)
				errorConflictingDefElem(fc_defel, fc_pstate);
			if (fc_is_procedure)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("invalid attribute in procedure definition"),
						 parser_errposition(fc_pstate, fc_defel->location)));
			fc_windowfunc_item = fc_defel;
		}
		else if (fc_compute_common_attribute(fc_pstate,
										  fc_is_procedure,
										  fc_defel,
										  &fc_volatility_item,
										  &fc_strict_item,
										  &fc_security_item,
										  &fc_leakproof_item,
										  &fc_set_items,
										  &fc_cost_item,
										  &fc_rows_item,
										  &fc_support_item,
										  &fc_parallel_item))
		{
			/* 识别的常见选项 */
			continue;
		}
		else
			elog(ERROR, "option \"%s\" not recognized",
				 fc_defel->defname);
	}

	if (fc_as_item)
		*fc_as = (List *) fc_as_item->arg;
	if (fc_language_item)
		*fc_language = strVal(fc_language_item->arg);
	if (fc_transform_item)
		*fc_transform = fc_transform_item->arg;
	if (fc_windowfunc_item)
		*fc_windowfunc_p = boolVal(fc_windowfunc_item->arg);
	if (fc_volatility_item)
		*fc_volatility_p = fc_interpret_func_volatility(fc_volatility_item);
	if (fc_strict_item)
		*fc_strict_p = boolVal(fc_strict_item->arg);
	if (fc_security_item)
		*fc_security_definer = boolVal(fc_security_item->arg);
	if (fc_leakproof_item)
		*fc_leakproof_p = boolVal(fc_leakproof_item->arg);
	if (fc_set_items)
		*fc_proconfig = fc_update_proconfig_value(NULL, fc_set_items);
	if (fc_cost_item)
	{
		*fc_procost = defGetNumeric(fc_cost_item);
		if (*fc_procost <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("COST must be positive")));
	}
	if (fc_rows_item)
	{
		*fc_prorows = defGetNumeric(fc_rows_item);
		if (*fc_prorows <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("ROWS must be positive")));
	}
	if (fc_support_item)
		*fc_prosupport = fc_interpret_func_support(fc_support_item);
	if (fc_parallel_item)
		*fc_parallel_p = fc_interpret_func_parallel(fc_parallel_item);
}


/*
 * 对于动态链接的 C 语言对象，子句的形式是
 *
 *	   AS <对象文件名> [, <链接符号名称> ]
 *
 * 在所有其他情况下
 *
 *	   AS <对象引用，或 SQL 代码>
 */
static void fc_interpret_AS_clause(Oid fc_languageOid, const char *fc_languageName,
					char *fc_funcname, List *fc_as, Node *fc_sql_body_in,
					List *fc_parameterTypes, List *fc_inParameterNames,
					char **fc_prosrc_str_p, char **fc_probin_str_p,
					Node **fc_sql_body_out,
					const char *fc_queryString)
{
	if (!fc_sql_body_in && !fc_as)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("no function body specified")));

	if (fc_sql_body_in && fc_as)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("duplicate function body specified")));

	if (fc_sql_body_in && fc_languageOid != SQLlanguageId)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("inline SQL function body only valid for language SQL")));

	*fc_sql_body_out = NULL;

	if (fc_languageOid == ClanguageId)
	{
		/*
		 * 对于 "C" 语言，将文件名存储在 probin 中，当给出时，
		 * 将链接符号名称存储在 prosrc 中。如果省略了链接符号，
		 * 则用过程名称替代。我们还允许将链接符号指定为 "-"，
		 * 因为在 PG 8.4 之前的版本中这是习惯，并且可能存在
		 * 一些转储文件未将其翻译回 "省略"。
		 */
		*fc_probin_str_p = strVal(linitial(fc_as));
		if (list_length(fc_as) == 1)
			*fc_prosrc_str_p = fc_funcname;
		else
		{
			*fc_prosrc_str_p = strVal(lsecond(fc_as));
			if (strcmp(*fc_prosrc_str_p, "-") == 0)
				*fc_prosrc_str_p = fc_funcname;
		}
	}
	else if (fc_sql_body_in)
	{
		SQLFunctionParseInfoPtr fc_pinfo;

		fc_pinfo = (SQLFunctionParseInfoPtr) palloc0(sizeof(SQLFunctionParseInfo));

		fc_pinfo->fname = fc_funcname;
		fc_pinfo->nargs = list_length(fc_parameterTypes);
		fc_pinfo->argtypes = (Oid *) palloc(fc_pinfo->nargs * sizeof(Oid));
		fc_pinfo->argnames = (char **) palloc(fc_pinfo->nargs * sizeof(char *));
		for (int fc_i = 0; fc_i < list_length(fc_parameterTypes); fc_i++)
		{
			char	   *fc_s = strVal(list_nth(fc_inParameterNames, fc_i));

			fc_pinfo->argtypes[fc_i] = list_nth_oid(fc_parameterTypes, fc_i);
			if (IsPolymorphicType(fc_pinfo->argtypes[fc_i]))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
						 errmsg("SQL function with unquoted function body cannot have polymorphic arguments")));

			if (fc_s[0] != '\0')
				fc_pinfo->argnames[fc_i] = fc_s;
			else
				fc_pinfo->argnames[fc_i] = NULL;
		}

		if (IsA(fc_sql_body_in, List))
		{
			List	   *fc_stmts = linitial_node(List, castNode(List, fc_sql_body_in));
			ListCell   *fc_lc;
			List	   *fc_transformed_stmts = NIL;

			foreach(fc_lc, fc_stmts)
			{
				Node	   *fc_stmt = lfirst(fc_lc);
				Query	   *fc_q;
				ParseState *fc_pstate = make_parsestate(NULL);

				fc_pstate->p_sourcetext = fc_queryString;
				sql_fn_parser_setup(fc_pstate, fc_pinfo);
				fc_q = transformStmt(fc_pstate, fc_stmt);
				if (fc_q->commandType == CMD_UTILITY)
					ereport(ERROR,
							errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							errmsg("%s is not yet supported in unquoted SQL function body",
								   GetCommandTagName(CreateCommandTag(fc_q->utilityStmt))));
				fc_transformed_stmts = lappend(fc_transformed_stmts, fc_q);
				free_parsestate(fc_pstate);
			}

			*fc_sql_body_out = (Node *) list_make1(fc_transformed_stmts);
		}
		else
		{
			Query	   *fc_q;
			ParseState *fc_pstate = make_parsestate(NULL);

			fc_pstate->p_sourcetext = fc_queryString;
			sql_fn_parser_setup(fc_pstate, fc_pinfo);
			fc_q = transformStmt(fc_pstate, fc_sql_body_in);
			if (fc_q->commandType == CMD_UTILITY)
				ereport(ERROR,
						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						errmsg("%s is not yet supported in unquoted SQL function body",
							   GetCommandTagName(CreateCommandTag(fc_q->utilityStmt))));
			free_parsestate(fc_pstate);

			*fc_sql_body_out = (Node *) fc_q;
		}

		/*
		 * 我们必须在 prosrc 中放置某些内容。暂时只记录一个
		 * 空字符串。记录 CREATE FUNCTION 语句的原始文本可能很有用——
		 * 但是为了在错误报告中实际使用它，我们还必须调整 readfuncs.c 
		 * 以便在读取 prosqlbody 时不丢弃节点位置字段。
		 */
		*fc_prosrc_str_p = pstrdup("");

		/* 但我们绝对不需要 probin。 */
		*fc_probin_str_p = NULL;
	}
	else
	{
		/* 其他内容希望在 prosrc 中给定字符串。 */
		*fc_prosrc_str_p = strVal(linitial(fc_as));
		*fc_probin_str_p = NULL;

		if (list_length(fc_as) != 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("only one AS item needed for language \"%s\"",
							fc_languageName)));

		if (fc_languageOid == INTERNALlanguageId)
		{
			/*
			 * 在 PostgreSQL 6.5 之前的版本中，创建的函数的 SQL 名称
			 * 不能与内部名称不同，并且 "prosrc" 没有被使用。因此，存在
			 * CREATE FUNCTION xyz AS '' LANGUAGE internal 的代码。为了
			 * 保持某种程度的向后兼容，接受空的 "prosrc" 值表示提供的 SQL 函数名称。
			 */
			if (strlen(*fc_prosrc_str_p) == 0)
				*fc_prosrc_str_p = fc_funcname;
		}
	}
}


/*
 * CreateFunction
 *	 执行 CREATE FUNCTION（或 CREATE PROCEDURE）实用语句。
 */
ObjectAddress CreateFunction(ParseState *fc_pstate, CreateFunctionStmt *fc_stmt)
{
	char	   *fc_probin_str;
	char	   *fc_prosrc_str;
	Node	   *fc_prosqlbody;
	Oid			fc_prorettype;
	bool		fc_returnsSet;
	char	   *fc_language;
	Oid			fc_languageOid;
	Oid			fc_languageValidator;
	Node	   *fc_transformDefElem = NULL;
	char	   *fc_funcname;
	Oid			fc_namespaceId;
	AclResult	fc_aclresult;
	oidvector  *fc_parameterTypes;
	List	   *fc_parameterTypes_list = NIL;
	ArrayType  *fc_allParameterTypes;
	ArrayType  *fc_parameterModes;
	ArrayType  *fc_parameterNames;
	List	   *fc_inParameterNames_list = NIL;
	List	   *fc_parameterDefaults;
	Oid			fc_variadicArgType;
	List	   *fc_trftypes_list = NIL;
	ArrayType  *fc_trftypes;
	Oid			fc_requiredResultType;
	bool		fc_isWindowFunc,
				fc_isStrict,
				fc_security,
				fc_isLeakProof;
	char		fc_volatility;
	ArrayType  *fc_proconfig;
	float4		fc_procost;
	float4		fc_prorows;
	Oid			fc_prosupport;
	HeapTuple	fc_languageTuple;
	Form_pg_language fc_languageStruct;
	List	   *fc_as_clause;
	char		fc_parallel;

	/* 将名称列表转换为一个名称和命名空间 */
	fc_namespaceId = QualifiedNameGetCreationNamespace(fc_stmt->funcname,
													&fc_funcname);

	/* 检查我们在目标命名空间中是否有创建权限 */
	fc_aclresult = pg_namespace_aclcheck(fc_namespaceId, GetUserId(), ACL_CREATE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_SCHEMA,
					   get_namespace_name(fc_namespaceId));

	/* 设置默认属性 */
	fc_as_clause = NIL;
	fc_language = NULL;
	fc_isWindowFunc = false;
	fc_isStrict = false;
	fc_security = false;
	fc_isLeakProof = false;
	fc_volatility = PROVOLATILE_VOLATILE;
	fc_proconfig = NULL;
	fc_procost = -1;				/* 表示未设置 */
	fc_prorows = -1;				/* 表示未设置 */
	fc_prosupport = InvalidOid;
	fc_parallel = PROPARALLEL_UNSAFE;

	/* 从 stmt->options 列表中提取非默认属性 */
	fc_compute_function_attributes(fc_pstate,
								fc_stmt->is_procedure,
								fc_stmt->options,
								&fc_as_clause, &fc_language, &fc_transformDefElem,
								&fc_isWindowFunc, &fc_volatility,
								&fc_isStrict, &fc_security, &fc_isLeakProof,
								&fc_proconfig, &fc_procost, &fc_prorows,
								&fc_prosupport, &fc_parallel);

	if (!fc_language)
	{
		if (fc_stmt->sql_body)
			fc_language = "sql";
		else
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("no language specified")));
	}

	/* 查找语言并验证权限 */
	fc_languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(fc_language));
	if (!HeapTupleIsValid(fc_languageTuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("language \"%s\" does not exist", fc_language),
				 (extension_file_exists(fc_language) ?
				  errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

	fc_languageStruct = (Form_pg_language) GETSTRUCT(fc_languageTuple);
	fc_languageOid = fc_languageStruct->oid;

	if (fc_languageStruct->lanpltrusted)
	{
		/* 如果是受信任的语言，需要 USAGE 特权 */
		AclResult	fc_aclresult;

		fc_aclresult = pg_language_aclcheck(fc_languageOid, GetUserId(), ACL_USAGE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_LANGUAGE,
						   NameStr(fc_languageStruct->lanname));
	}
	else
	{
		/* 如果是非受信任的语言，必须是超级用户 */
		if (!superuser())
			aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE,
						   NameStr(fc_languageStruct->lanname));
	}

	fc_languageValidator = fc_languageStruct->lanvalidator;

	ReleaseSysCache(fc_languageTuple);

	/*
	 * 只有超级用户被允许创建防泄漏函数，因为防泄漏函数可以看到
	 * 尚未被安全屏障视图或行级安全策略过滤掉的元组。
	 */
	if (fc_isLeakProof && !superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("only superuser can define a leakproof function")));

	if (fc_transformDefElem)
	{
		ListCell   *fc_lc;

		foreach(fc_lc, castNode(List, fc_transformDefElem))
		{
			Oid			fc_typeid = typenameTypeId(NULL,
												lfirst_node(TypeName, fc_lc));
			Oid			fc_elt = get_base_element_type(fc_typeid);

			fc_typeid = fc_elt ? fc_elt : fc_typeid;

			get_transform_oid(fc_typeid, fc_languageOid, false);
			fc_trftypes_list = lappend_oid(fc_trftypes_list, fc_typeid);
		}
	}

	/*
	 * 将 CREATE 的剩余参数转换为 ProcedureCreate 所需的形式。
	 */
	interpret_function_parameter_list(fc_pstate,
									  fc_stmt->parameters,
									  fc_languageOid,
									  fc_stmt->is_procedure ? OBJECT_PROCEDURE : OBJECT_FUNCTION,
									  &fc_parameterTypes,
									  &fc_parameterTypes_list,
									  &fc_allParameterTypes,
									  &fc_parameterModes,
									  &fc_parameterNames,
									  &fc_inParameterNames_list,
									  &fc_parameterDefaults,
									  &fc_variadicArgType,
									  &fc_requiredResultType);

	if (fc_stmt->is_procedure)
	{
		Assert(!fc_stmt->returnType);
		fc_prorettype = fc_requiredResultType ? fc_requiredResultType : VOIDOID;
		fc_returnsSet = false;
	}
	else if (fc_stmt->returnType)
	{
		/* 显式 RETURNS 子句 */
		fc_compute_return_type(fc_stmt->returnType, fc_languageOid,
							&fc_prorettype, &fc_returnsSet);
		if (OidIsValid(fc_requiredResultType) && fc_prorettype != fc_requiredResultType)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
					 errmsg("function result type must be %s because of OUT parameters",
							format_type_be(fc_requiredResultType))));
	}
	else if (OidIsValid(fc_requiredResultType))
	{
		/* 从 OUT 参数中获取默认 RETURNS 子句 */
		fc_prorettype = fc_requiredResultType;
		fc_returnsSet = false;
	}
	else
	{
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
				 errmsg("function result type must be specified")));
		/* 替代可能性：默认为 RETURNS VOID */
		fc_prorettype = VOIDOID;
		fc_returnsSet = false;
	}

	if (list_length(fc_trftypes_list) > 0)
	{
		ListCell   *fc_lc;
		Datum	   *fc_arr;
		int			fc_i;

		fc_arr = palloc(list_length(fc_trftypes_list) * sizeof(Datum));
		fc_i = 0;
		foreach(fc_lc, fc_trftypes_list)
			fc_arr[fc_i++] = ObjectIdGetDatum(lfirst_oid(fc_lc));
		fc_trftypes = construct_array(fc_arr, list_length(fc_trftypes_list),
								   OIDOID, sizeof(Oid), true, TYPALIGN_INT);
	}
	else
	{
		/* 存储 SQL NULL 而不是空数组 */
		fc_trftypes = NULL;
	}

	fc_interpret_AS_clause(fc_languageOid, fc_language, fc_funcname, fc_as_clause, fc_stmt->sql_body,
						fc_parameterTypes_list, fc_inParameterNames_list,
						&fc_prosrc_str, &fc_probin_str, &fc_prosqlbody,
						fc_pstate->p_sourcetext);

	/*
	 * 根据其他参数设置 COST 和 ROWS 的默认值；
	 * 如果 ROWS 不是 returnsSet，则拒绝。注意：pg_dump 知道这些默认
	 * 值，如果更改它们，请保持同步。
	 */
	if (fc_procost < 0)
	{
		/* SQL 和 PL 语言函数通常被认为更昂贵 */
		if (fc_languageOid == INTERNALlanguageId ||
			fc_languageOid == ClanguageId)
			fc_procost = 1;
		else
			fc_procost = 100;
	}
	if (fc_prorows < 0)
	{
		if (fc_returnsSet)
			fc_prorows = 1000;
		else
			fc_prorows = 0;		/* 如果不是 returnsSet 的虚拟值 */
	}
	else if (!fc_returnsSet)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("ROWS is not applicable when function does not return a set")));

	/*
	 * 现在我们已经有了所有参数，并且知道我们被允许这样做，
	 * 继续创建函数。
	 */
	return ProcedureCreate(fc_funcname,
						   fc_namespaceId,
						   fc_stmt->replace,
						   fc_returnsSet,
						   fc_prorettype,
						   GetUserId(),
						   fc_languageOid,
						   fc_languageValidator,
						   fc_prosrc_str,	/* 稍后转换为文本 */
						   fc_probin_str,	/* 稍后转换为文本 */
						   fc_prosqlbody,
						   fc_stmt->is_procedure ? PROKIND_PROCEDURE : (fc_isWindowFunc ? PROKIND_WINDOW : PROKIND_FUNCTION),
						   fc_security,
						   fc_isLeakProof,
						   fc_isStrict,
						   fc_volatility,
						   fc_parallel,
						   fc_parameterTypes,
						   PointerGetDatum(fc_allParameterTypes),
						   PointerGetDatum(fc_parameterModes),
						   PointerGetDatum(fc_parameterNames),
						   fc_parameterDefaults,
						   PointerGetDatum(fc_trftypes),
						   PointerGetDatum(fc_proconfig),
						   fc_prosupport,
						   fc_procost,
						   fc_prorows);
}

/*
 * 函数删除的核心部分。
 *
 * 注意：这也用于聚合删除，因为函数和聚合的 OID
 * 都指向 pg_proc。
 */
void RemoveFunctionById(Oid fc_funcOid)
{
	Relation	fc_relation;
	HeapTuple	fc_tup;
	char		fc_prokind;

	/*
	 * 删除 pg_proc 元组。
	 */
	fc_relation = table_open(ProcedureRelationId, RowExclusiveLock);

	fc_tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcOid));
	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for function %u", fc_funcOid);

	fc_prokind = ((Form_pg_proc) GETSTRUCT(fc_tup))->prokind;

	CatalogTupleDelete(fc_relation, &fc_tup->t_self);

	ReleaseSysCache(fc_tup);

	table_close(fc_relation, RowExclusiveLock);

	pgstat_drop_function(fc_funcOid);

	/*
	 * 如果有 pg_aggregate 元组，也删除它。
	 */
	if (fc_prokind == PROKIND_AGGREGATE)
	{
		fc_relation = table_open(AggregateRelationId, RowExclusiveLock);

		fc_tup = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(fc_funcOid));
		if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
			elog(ERROR, "cache lookup failed for pg_aggregate tuple for function %u", fc_funcOid);

		CatalogTupleDelete(fc_relation, &fc_tup->t_self);

		ReleaseSysCache(fc_tup);

		table_close(fc_relation, RowExclusiveLock);
	}
}

/*
 * 实现 ALTER FUNCTION 实用命令（除了 RENAME 和 OWNER 子句，
 * 这些作为通用 ALTER 框架的一部分处理）。
 */
ObjectAddress AlterFunction(ParseState *fc_pstate, AlterFunctionStmt *fc_stmt)
{
	HeapTuple	fc_tup;
	Oid			fc_funcOid;
	Form_pg_proc fc_procForm;
	bool		fc_is_procedure;
	Relation	fc_rel;
	ListCell   *fc_l;
	DefElem    *fc_volatility_item = NULL;
	DefElem    *fc_strict_item = NULL;
	DefElem    *fc_security_def_item = NULL;
	DefElem    *fc_leakproof_item = NULL;
	List	   *fc_set_items = NIL;
	DefElem    *fc_cost_item = NULL;
	DefElem    *fc_rows_item = NULL;
	DefElem    *fc_support_item = NULL;
	DefElem    *fc_parallel_item = NULL;
	ObjectAddress fc_address;

	fc_rel = table_open(ProcedureRelationId, RowExclusiveLock);

	fc_funcOid = LookupFuncWithArgs(fc_stmt->objtype, fc_stmt->func, false);

	ObjectAddressSet(fc_address, ProcedureRelationId, fc_funcOid);

	fc_tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(fc_funcOid));
	if (!HeapTupleIsValid(fc_tup)) /* 不应该发生 */
		elog(ERROR, "cache lookup failed for function %u", fc_funcOid);

	fc_procForm = (Form_pg_proc) GETSTRUCT(fc_tup);

	/* 权限检查：必须拥有函数 */
	if (!pg_proc_ownercheck(fc_funcOid, GetUserId()))
		aclcheck_error(ACLCHECK_NOT_OWNER, fc_stmt->objtype,
					   NameListToString(fc_stmt->func->objname));

	if (fc_procForm->prokind == PROKIND_AGGREGATE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is an aggregate function",
						NameListToString(fc_stmt->func->objname))));

	fc_is_procedure = (fc_procForm->prokind == PROKIND_PROCEDURE);

	/* 检查请求的操作。 */
	foreach(fc_l, fc_stmt->actions)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);

		if (fc_compute_common_attribute(fc_pstate,
									 fc_is_procedure,
									 fc_defel,
									 &fc_volatility_item,
									 &fc_strict_item,
									 &fc_security_def_item,
									 &fc_leakproof_item,
									 &fc_set_items,
									 &fc_cost_item,
									 &fc_rows_item,
									 &fc_support_item,
									 &fc_parallel_item) == false)
			elog(ERROR, "option \"%s\" not recognized", fc_defel->defname);
	}

	if (fc_volatility_item)
		fc_procForm->provolatile = fc_interpret_func_volatility(fc_volatility_item);
	if (fc_strict_item)
		fc_procForm->proisstrict = boolVal(fc_strict_item->arg);
	if (fc_security_def_item)
		fc_procForm->prosecdef = boolVal(fc_security_def_item->arg);
	if (fc_leakproof_item)
	{
		fc_procForm->proleakproof = boolVal(fc_leakproof_item->arg);
		if (fc_procForm->proleakproof && !superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("only superuser can define a leakproof function")));
	}
	if (fc_cost_item)
	{
		fc_procForm->procost = defGetNumeric(fc_cost_item);
		if (fc_procForm->procost <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("COST must be positive")));
	}
	if (fc_rows_item)
	{
		fc_procForm->prorows = defGetNumeric(fc_rows_item);
		if (fc_procForm->prorows <= 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("ROWS must be positive")));
		if (!fc_procForm->proretset)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("ROWS is not applicable when function does not return a set")));
	}
	if (fc_support_item)
	{
		/* interpret_func_support 处理特权检查 */
		Oid			fc_newsupport = fc_interpret_func_support(fc_support_item);

		/* 添加或替换对支持函数的依赖关系 */
		if (OidIsValid(fc_procForm->prosupport))
			changeDependencyFor(ProcedureRelationId, fc_funcOid,
								ProcedureRelationId, fc_procForm->prosupport,
								fc_newsupport);
		else
		{
			ObjectAddress fc_referenced;

			fc_referenced.classId = ProcedureRelationId;
			fc_referenced.objectId = fc_newsupport;
			fc_referenced.objectSubId = 0;
			recordDependencyOn(&fc_address, &fc_referenced, DEPENDENCY_NORMAL);
		}

		fc_procForm->prosupport = fc_newsupport;
	}
	if (fc_parallel_item)
		fc_procForm->proparallel = fc_interpret_func_parallel(fc_parallel_item);
	if (fc_set_items)
	{
		Datum		fc_datum;
		bool		fc_isnull;
		ArrayType  *fc_a;
		Datum		fc_repl_val[Natts_pg_proc];
		bool		fc_repl_null[Natts_pg_proc];
		bool		fc_repl_repl[Natts_pg_proc];

		/* 提取现有的 proconfig 设置 */
		fc_datum = SysCacheGetAttr(PROCOID, fc_tup, Anum_pg_proc_proconfig, &fc_isnull);
		fc_a = fc_isnull ? NULL : DatumGetArrayTypeP(fc_datum);

		/* 根据每个 SET 或 RESET 项从左到右更新 */
		fc_a = fc_update_proconfig_value(fc_a, fc_set_items);

		/* 更新元组 */
		memset(fc_repl_repl, false, sizeof(fc_repl_repl));
		fc_repl_repl[Anum_pg_proc_proconfig - 1] = true;

		if (fc_a == NULL)
		{
			fc_repl_val[Anum_pg_proc_proconfig - 1] = (Datum) 0;
			fc_repl_null[Anum_pg_proc_proconfig - 1] = true;
		}
		else
		{
			fc_repl_val[Anum_pg_proc_proconfig - 1] = PointerGetDatum(fc_a);
			fc_repl_null[Anum_pg_proc_proconfig - 1] = false;
		}

		fc_tup = heap_modify_tuple(fc_tup, RelationGetDescr(fc_rel),
								fc_repl_val, fc_repl_null, fc_repl_repl);
	}
	/* 不要在这里再添加 procForm 的更多内容；现在它是悬而未决的。 */

	/* 执行更新 */
	CatalogTupleUpdate(fc_rel, &fc_tup->t_self, fc_tup);

	InvokeObjectPostAlterHook(ProcedureRelationId, fc_funcOid, 0);

	table_close(fc_rel, NoLock);
	heap_freetuple(fc_tup);

	return fc_address;
}


/*
 * CREATE CAST
 */
ObjectAddress CreateCast(CreateCastStmt *fc_stmt)
{
	Oid			fc_sourcetypeid;
	Oid			fc_targettypeid;
	char		fc_sourcetyptype;
	char		fc_targettyptype;
	Oid			fc_funcid;
	int			fc_nargs;
	char		fc_castcontext;
	char		fc_castmethod;
	HeapTuple	fc_tuple;
	AclResult	fc_aclresult;
	ObjectAddress fc_myself;

	fc_sourcetypeid = typenameTypeId(NULL, fc_stmt->sourcetype);
	fc_targettypeid = typenameTypeId(NULL, fc_stmt->targettype);
	fc_sourcetyptype = get_typtype(fc_sourcetypeid);
	fc_targettyptype = get_typtype(fc_targettypeid);

	/* 不允许伪类型 */
	if (fc_sourcetyptype == TYPTYPE_PSEUDO)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("source data type %s is a pseudo-type",
						TypeNameToString(fc_stmt->sourcetype))));

	if (fc_targettyptype == TYPTYPE_PSEUDO)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("target data type %s is a pseudo-type",
						TypeNameToString(fc_stmt->targettype))));

	/* 权限检查 */
	if (!pg_type_ownercheck(fc_sourcetypeid, GetUserId())
		&& !pg_type_ownercheck(fc_targettypeid, GetUserId()))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be owner of type %s or type %s",
						format_type_be(fc_sourcetypeid),
						format_type_be(fc_targettypeid))));

	fc_aclresult = pg_type_aclcheck(fc_sourcetypeid, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_sourcetypeid);

	fc_aclresult = pg_type_aclcheck(fc_targettypeid, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_targettypeid);

	/* 由于历史原因，允许使用领域，但我们警告 */  
	if (fc_sourcetyptype == TYPTYPE_DOMAIN)
		ereport(WARNING,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cast will be ignored because the source data type is a domain")));

	else if (fc_targettyptype == TYPTYPE_DOMAIN)
		ereport(WARNING,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cast will be ignored because the target data type is a domain")));

	/* 确定转换方法 */  
	if (fc_stmt->func != NULL)
		fc_castmethod = COERCION_METHOD_FUNCTION;
	else if (fc_stmt->inout)
		fc_castmethod = COERCION_METHOD_INOUT;
	else
		fc_castmethod = COERCION_METHOD_BINARY;

	if (fc_castmethod == COERCION_METHOD_FUNCTION)
	{
		Form_pg_proc fc_procstruct;

		fc_funcid = LookupFuncWithArgs(OBJECT_FUNCTION, fc_stmt->func, false);

		fc_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_funcid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for function %u", fc_funcid);

		fc_procstruct = (Form_pg_proc) GETSTRUCT(fc_tuple);
		fc_nargs = fc_procstruct->pronargs;
		if (fc_nargs < 1 || fc_nargs > 3)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cast function must take one to three arguments")));
		if (!IsBinaryCoercible(fc_sourcetypeid, fc_procstruct->proargtypes.values[0]))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("argument of cast function must match or be binary-coercible from source data type")));
		if (fc_nargs > 1 && fc_procstruct->proargtypes.values[1] != INT4OID)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("second argument of cast function must be type %s",
							"integer")));
		if (fc_nargs > 2 && fc_procstruct->proargtypes.values[2] != BOOLOID)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("third argument of cast function must be type %s",
							"boolean")));
		if (!IsBinaryCoercible(fc_procstruct->prorettype, fc_targettypeid))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("return data type of cast function must match or be binary-coercible to target data type")));

		/*  
		 * 限制转换函数的波动性在抽象上可能好或坏，但它肯定会破坏许多旧的用户定义类型。禁用此检查 --- tgl 2/1/03  
		 */  
#ifdef NOT_USED
		if (fc_procstruct->provolatile == PROVOLATILE_VOLATILE)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cast function must not be volatile")));
#endif
		if (fc_procstruct->prokind != PROKIND_FUNCTION)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cast function must be a normal function")));
		if (fc_procstruct->proretset)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("cast function must not return a set")));

		ReleaseSysCache(fc_tuple);
	}
	else
	{
		fc_funcid = InvalidOid;
		fc_nargs = 0;
	}

	if (fc_castmethod == COERCION_METHOD_BINARY)
	{
		int16		fc_typ1len;
		int16		fc_typ2len;
		bool		fc_typ1byval;
		bool		fc_typ2byval;
		char		fc_typ1align;
		char		fc_typ2align;

		/*  
		 * 创建二进制兼容的转换必须是超级用户，因为  
		 * 错误的转换可以轻易地导致后台崩溃。  
		 */  
		if (!superuser())
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("must be superuser to create a cast WITHOUT FUNCTION")));

		/*  
		 * 此外，确保类型在大小、对齐和  
		 * 按值传递属性上匹配；这至少提供了一个粗略的检查，  
		 * 以确保它们具有相似的表示形式。未能通过此  
		 * 测试的一对类型肯定不应被等同。  
		 */  
		get_typlenbyvalalign(fc_sourcetypeid, &fc_typ1len, &fc_typ1byval, &fc_typ1align);
		get_typlenbyvalalign(fc_targettypeid, &fc_typ2len, &fc_typ2byval, &fc_typ2align);
		if (fc_typ1len != fc_typ2len ||
			fc_typ1byval != fc_typ2byval ||
			fc_typ1align != fc_typ2align)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("source and target data types are not physically compatible")));

		/*  
		 * 我们知道复合类型、枚举类型和数组类型之间永远不会  
		 * 彼此二进制兼容。它们都嵌入了 OID。  
		 *  
		 * 理论上，你可以构建一个用户定义的基本类型，其  
		 * 二进制兼容性与复合类型、枚举类型或数组类型相同。  
		 * 但我们也不允许这样，因为在实践中，这样的转换肯定是错误的。  
		 * 你总是可以通过编写转换函数来解决这个问题。  
		 */  
		if (fc_sourcetyptype == TYPTYPE_COMPOSITE ||
			fc_targettyptype == TYPTYPE_COMPOSITE)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("composite data types are not binary-compatible")));

		if (fc_sourcetyptype == TYPTYPE_ENUM ||
			fc_targettyptype == TYPTYPE_ENUM)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("enum data types are not binary-compatible")));

		if (OidIsValid(get_element_type(fc_sourcetypeid)) ||
			OidIsValid(get_element_type(fc_targettypeid)))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("array data types are not binary-compatible")));

		/*  
		 * 我们还不允许创建涉及  
		 * 领域的二进制兼容转换。从领域到其基本类型的转换已被  
		 * 允许，而反向转换应该通过领域  
		 * 强制转换以允许约束检查。再次，如果你想要实现自己的  
		 * 语义，可以创建一个无操作的转换函数。  
		 *  
		 * 注意：如果我们放宽这一点，上述对复合类型等的检查  
		 * 将必须修改以查看领域到其基本类型的内容。  
		 */  
		if (fc_sourcetyptype == TYPTYPE_DOMAIN ||
			fc_targettyptype == TYPTYPE_DOMAIN)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("domain data types must not be marked binary-compatible")));
	}

	/*  
	 * 只允许源类型和目标类型在长度强制转换函数中相同。  
	 * 我们假设多参数函数执行长度强制转换。  
	 */  
	if (fc_sourcetypeid == fc_targettypeid && fc_nargs < 2)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("source data type and target data type are the same")));

	/* 将 CoercionContext 枚举转换为 castcontext 的字符值 */  
	switch (fc_stmt->context)
	{
		case COERCION_IMPLICIT:
			fc_castcontext = COERCION_CODE_IMPLICIT;
			break;
		case COERCION_ASSIGNMENT:
			fc_castcontext = COERCION_CODE_ASSIGNMENT;
			break;
			/* COERCION_PLPGSQL 故意未在此覆盖 */  
		case COERCION_EXPLICIT:
			fc_castcontext = COERCION_CODE_EXPLICIT;
			break;
		default:
			elog(ERROR, "unrecognized CoercionContext: %d", fc_stmt->context);
			fc_castcontext = 0;	/* 保持编译器安静 */
			break;
	}

	fc_myself = CastCreate(fc_sourcetypeid, fc_targettypeid, fc_funcid, fc_castcontext,
						fc_castmethod, DEPENDENCY_NORMAL);
	return fc_myself;
}


static void fc_check_transform_function(Form_pg_proc fc_procstruct)
{
	if (fc_procstruct->provolatile == PROVOLATILE_VOLATILE)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("transform function must not be volatile")));
	if (fc_procstruct->prokind != PROKIND_FUNCTION)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("transform function must be a normal function")));
	if (fc_procstruct->proretset)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("transform function must not return a set")));
	if (fc_procstruct->pronargs != 1)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("transform function must take one argument")));
	if (fc_procstruct->proargtypes.values[0] != INTERNALOID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("first argument of transform function must be type %s",
						"internal")));
}


/* 创建转换 */  
ObjectAddress CreateTransform(CreateTransformStmt *fc_stmt)
{
	Oid			fc_typeid;
	char		fc_typtype;
	Oid			fc_langid;
	Oid			fc_fromsqlfuncid;
	Oid			fc_tosqlfuncid;
	AclResult	fc_aclresult;
	Form_pg_proc fc_procstruct;
	Datum		fc_values[Natts_pg_transform];
	bool		fc_nulls[Natts_pg_transform];
	bool		fc_replaces[Natts_pg_transform];
	Oid			fc_transformid;
	HeapTuple	fc_tuple;
	HeapTuple	fc_newtuple;
	Relation	fc_relation;
	ObjectAddress fc_myself,
				fc_referenced;
	ObjectAddresses *fc_addrs;
	bool		fc_is_replace;

	/*  
	 * 获取类型  
	 */  
	fc_typeid = typenameTypeId(NULL, fc_stmt->type_name);
	fc_typtype = get_typtype(fc_typeid);

	if (fc_typtype == TYPTYPE_PSEUDO)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("data type %s is a pseudo-type",
						TypeNameToString(fc_stmt->type_name))));

	if (fc_typtype == TYPTYPE_DOMAIN)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("data type %s is a domain",
						TypeNameToString(fc_stmt->type_name))));

	if (!pg_type_ownercheck(fc_typeid, GetUserId()))
		aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typeid);

	fc_aclresult = pg_type_aclcheck(fc_typeid, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error_type(fc_aclresult, fc_typeid);

	/*  
	 * 获取语言  
	 */  
	fc_langid = get_language_oid(fc_stmt->lang, false);

	fc_aclresult = pg_language_aclcheck(fc_langid, GetUserId(), ACL_USAGE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_LANGUAGE, fc_stmt->lang);

	/*  
	 * 获取函数  
	 */  
	if (fc_stmt->fromsql)
	{
		fc_fromsqlfuncid = LookupFuncWithArgs(OBJECT_FUNCTION, fc_stmt->fromsql, false);

		if (!pg_proc_ownercheck(fc_fromsqlfuncid, GetUserId()))
			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(fc_stmt->fromsql->objname));

		fc_aclresult = pg_proc_aclcheck(fc_fromsqlfuncid, GetUserId(), ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION, NameListToString(fc_stmt->fromsql->objname));

		fc_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_fromsqlfuncid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for function %u", fc_fromsqlfuncid);
		fc_procstruct = (Form_pg_proc) GETSTRUCT(fc_tuple);
		if (fc_procstruct->prorettype != INTERNALOID)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("return data type of FROM SQL function must be %s",
							"internal")));
		fc_check_transform_function(fc_procstruct);
		ReleaseSysCache(fc_tuple);
	}
	else
		fc_fromsqlfuncid = InvalidOid;

	if (fc_stmt->tosql)
	{
		fc_tosqlfuncid = LookupFuncWithArgs(OBJECT_FUNCTION, fc_stmt->tosql, false);

		if (!pg_proc_ownercheck(fc_tosqlfuncid, GetUserId()))
			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_FUNCTION, NameListToString(fc_stmt->tosql->objname));

		fc_aclresult = pg_proc_aclcheck(fc_tosqlfuncid, GetUserId(), ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_FUNCTION, NameListToString(fc_stmt->tosql->objname));

		fc_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_tosqlfuncid));
		if (!HeapTupleIsValid(fc_tuple))
			elog(ERROR, "cache lookup failed for function %u", fc_tosqlfuncid);
		fc_procstruct = (Form_pg_proc) GETSTRUCT(fc_tuple);
		if (fc_procstruct->prorettype != fc_typeid)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
					 errmsg("return data type of TO SQL function must be the transform data type")));
		fc_check_transform_function(fc_procstruct);
		ReleaseSysCache(fc_tuple);
	}
	else
		fc_tosqlfuncid = InvalidOid;

	/*  
	 * 准备就绪  
	 */  
	fc_values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(fc_typeid);
	fc_values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(fc_langid);
	fc_values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fc_fromsqlfuncid);
	fc_values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(fc_tosqlfuncid);

	MemSet(fc_nulls, false, sizeof(fc_nulls));

	fc_relation = table_open(TransformRelationId, RowExclusiveLock);

	fc_tuple = SearchSysCache2(TRFTYPELANG,
							ObjectIdGetDatum(fc_typeid),
							ObjectIdGetDatum(fc_langid));
	if (HeapTupleIsValid(fc_tuple))
	{
		Form_pg_transform fc_form = (Form_pg_transform) GETSTRUCT(fc_tuple);

		if (!fc_stmt->replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("transform for type %s language \"%s\" already exists",
							format_type_be(fc_typeid),
							fc_stmt->lang)));

		MemSet(fc_replaces, false, sizeof(fc_replaces));
		fc_replaces[Anum_pg_transform_trffromsql - 1] = true;
		fc_replaces[Anum_pg_transform_trftosql - 1] = true;

		fc_newtuple = heap_modify_tuple(fc_tuple, RelationGetDescr(fc_relation), fc_values, fc_nulls, fc_replaces);
		CatalogTupleUpdate(fc_relation, &fc_newtuple->t_self, fc_newtuple);

		fc_transformid = fc_form->oid;
		ReleaseSysCache(fc_tuple);
		fc_is_replace = true;
	}
	else
	{
		fc_transformid = GetNewOidWithIndex(fc_relation, TransformOidIndexId,
										 Anum_pg_transform_oid);
		fc_values[Anum_pg_transform_oid - 1] = ObjectIdGetDatum(fc_transformid);
		fc_newtuple = heap_form_tuple(RelationGetDescr(fc_relation), fc_values, fc_nulls);
		CatalogTupleInsert(fc_relation, fc_newtuple);
		fc_is_replace = false;
	}

	if (fc_is_replace)
		deleteDependencyRecordsFor(TransformRelationId, fc_transformid, true);

	fc_addrs = new_object_addresses();

	/* 创建依赖项条目 */  
	ObjectAddressSet(fc_myself, TransformRelationId, fc_transformid);

	/* 对语言的依赖 */  
	ObjectAddressSet(fc_referenced, LanguageRelationId, fc_langid);
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对类型的依赖 */  
	ObjectAddressSet(fc_referenced, TypeRelationId, fc_typeid);
	add_exact_object_address(&fc_referenced, fc_addrs);

	/* 对函数的依赖 */  
	if (OidIsValid(fc_fromsqlfuncid))
	{
		ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_fromsqlfuncid);
		add_exact_object_address(&fc_referenced, fc_addrs);
	}
	if (OidIsValid(fc_tosqlfuncid))
	{
		ObjectAddressSet(fc_referenced, ProcedureRelationId, fc_tosqlfuncid);
		add_exact_object_address(&fc_referenced, fc_addrs);
	}

	record_object_address_dependencies(&fc_myself, fc_addrs, DEPENDENCY_NORMAL);
	free_object_addresses(fc_addrs);

	/* 对扩展的依赖 */
	recordDependencyOnCurrentExtension(&fc_myself, fc_is_replace);

	/* 新转换的后创建钩子 */  
	InvokeObjectPostCreateHook(TransformRelationId, fc_transformid, 0);

	heap_freetuple(fc_newtuple);

	table_close(fc_relation, RowExclusiveLock);

	return fc_myself;
}


/*  
 * get_transform_oid - 给定类型 OID 和语言 OID，查找转换 OID  
 *  
 * 如果 missing_ok 为 false，如果未找到转换，则引发错误。  
 * 如果为 true，则返回 InvalidOid。  
 */  
Oid get_transform_oid(Oid fc_type_id, Oid fc_lang_id, bool fc_missing_ok)
{
	Oid			fc_oid;

	fc_oid = GetSysCacheOid2(TRFTYPELANG, Anum_pg_transform_oid,
						  ObjectIdGetDatum(fc_type_id),
						  ObjectIdGetDatum(fc_lang_id));
	if (!OidIsValid(fc_oid) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("transform for type %s language \"%s\" does not exist",
						format_type_be(fc_type_id),
						get_language_name(fc_lang_id, false))));
	return fc_oid;
}


/*  
 * ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME 的子例程  
 *  
 * 在给定命名空间中是否已经存在具有给定名称和签名的函数？  
 * 如果是，抛出适当的错误消息。  
 */  
void IsThereFunctionInNamespace(const char *fc_proname, int fc_pronargs,
						   oidvector *fc_proargtypes, Oid fc_nspOid)
{
	/* 检查重复的名称（比唯一索引失败更友好） */
	if (SearchSysCacheExists3(PROCNAMEARGSNSP,
							  CStringGetDatum(fc_proname),
							  PointerGetDatum(fc_proargtypes),
							  ObjectIdGetDatum(fc_nspOid)))
		ereport(ERROR,
				(errcode(ERRCODE_DUPLICATE_FUNCTION),
				 errmsg("function %s already exists in schema \"%s\"",
						funcname_signature_string(fc_proname, fc_pronargs,
												  NIL, fc_proargtypes->values),
						get_namespace_name(fc_nspOid))));
}

/*  
 * ExecuteDoStmt  
 *		执行内联过程语言代码  
 *  
 * 请参见 ExecuteCallStmt() 了解原子的参数。  
 */  
void ExecuteDoStmt(ParseState *fc_pstate, DoStmt *fc_stmt, bool fc_atomic)
{
	InlineCodeBlock *fc_codeblock = makeNode(InlineCodeBlock);
	ListCell   *fc_arg;
	DefElem    *fc_as_item = NULL;
	DefElem    *fc_language_item = NULL;
	char	   *fc_language;
	Oid			fc_laninline;
	HeapTuple	fc_languageTuple;
	Form_pg_language fc_languageStruct;

	/* 处理我们从 gram.y 获取的选项 */
	foreach(fc_arg, fc_stmt->args)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_arg);

		if (strcmp(fc_defel->defname, "as") == 0)
		{
			if (fc_as_item)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_as_item = fc_defel;
		}
		else if (strcmp(fc_defel->defname, "language") == 0)
		{
			if (fc_language_item)
				errorConflictingDefElem(fc_defel, fc_pstate);
			fc_language_item = fc_defel;
		}
		else
			elog(ERROR, "option \"%s\" not recognized",
				 fc_defel->defname);
	}

	if (fc_as_item)
		fc_codeblock->source_text = strVal(fc_as_item->arg);
	else
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("no inline code specified")));

	/* 如果没有指定 LANGUAGE 选项，则使用默认值 */
	if (fc_language_item)
		fc_language = strVal(fc_language_item->arg);
	else
		fc_language = "plpgsql";

	/* 查找语言并验证权限 */
	fc_languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(fc_language));
	if (!HeapTupleIsValid(fc_languageTuple))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("language \"%s\" does not exist", fc_language),
				 (extension_file_exists(fc_language) ?
				  errhint("Use CREATE EXTENSION to load the language into the database.") : 0)));

	fc_languageStruct = (Form_pg_language) GETSTRUCT(fc_languageTuple);
	fc_codeblock->langOid = fc_languageStruct->oid;
	fc_codeblock->langIsTrusted = fc_languageStruct->lanpltrusted;
	fc_codeblock->atomic = fc_atomic;

	if (fc_languageStruct->lanpltrusted)
	{
		/* 如果是受信任的语言，需要 USAGE 特权 */
		AclResult	fc_aclresult;

		fc_aclresult = pg_language_aclcheck(fc_codeblock->langOid, GetUserId(),
										 ACL_USAGE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_LANGUAGE,
						   NameStr(fc_languageStruct->lanname));
	}
	else
	{
		/* 如果是非受信任的语言，必须是超级用户 */
		if (!superuser())
			aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_LANGUAGE,
						   NameStr(fc_languageStruct->lanname));
	}

	/* 获取处理程序函数的 OID */
	fc_laninline = fc_languageStruct->laninline;
	if (!OidIsValid(fc_laninline))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("language \"%s\" does not support inline code execution",
						NameStr(fc_languageStruct->lanname))));

	ReleaseSysCache(fc_languageTuple);

	/* 执行内联处理程序 */
	OidFunctionCall1(fc_laninline, PointerGetDatum(fc_codeblock));
}

/*
 * 执行 CALL 语句
 *
 * 在顶层 CALL 语句内部，允许终止事务的命令，例如
 * COMMIT 或 PL 特定的等效命令。SQL 标准中的术语是 CALL
 * 建立一个非原子执行上下文。大多数其他命令建立一个原子执行上下文，
 * 在此上下文中不允许事务控制操作。如果存在嵌套的 CALL 执行，
 * 我们希望递归跟踪执行上下文，以便嵌套的 CALL 也可以进行事务控制。
 * 注意，例如在 CALL -> SELECT -> CALL 中，第二个调用不能进行事务控制，
 * 因为中间的 SELECT 建立了一个原子执行上下文。
 *
 * 因此，当从顶层调用 ExecuteCallStmt() 时，我们传入
 * atomic = false（回忆一下，这意味着 transactions = yes）。
 * 然后我们创建一个内容为 atomic = false 的 CallContext 节点，
 * 该节点被传递到 fcinfo->context 字段以调用过程。
 * 然后语言实现应根据该信息采取适当措施允许或阻止事务命令，
 * 例如调用 SPI_connect_ext(SPI_OPT_NONATOMIC)。
 * 语言还应将 atomic 标志传递给任何嵌套的 CALL 调用。
 *
 * 我们在此函数内创建的表达式数据结构和执行上下文是
 * CALL 实用语句运行的 Portal 的 portalContext 的子项。
 * 因此，我们传递给过程的任何按引用传递的值将在
 * 过程内部发生的事务提交中保留。
 */
void ExecuteCallStmt(CallStmt *fc_stmt, ParamListInfo fc_params, bool fc_atomic, DestReceiver *fc_dest)
{
	LOCAL_FCINFO(fc_fcinfo, FUNC_MAX_ARGS);
	ListCell   *fc_lc;
	FuncExpr   *fc_fexpr;
	int			fc_nargs;
	int			fc_i;
	AclResult	fc_aclresult;
	FmgrInfo	fc_flinfo;
	CallContext *fc_callcontext;
	EState	   *fc_estate;
	ExprContext *fc_econtext;
	HeapTuple	fc_tp;
	PgStat_FunctionCallUsage fc_fcusage;
	Datum		fc_retval;

	fc_fexpr = fc_stmt->funcexpr;
	Assert(fc_fexpr);
	Assert(IsA(fc_fexpr, FuncExpr));

	fc_aclresult = pg_proc_aclcheck(fc_fexpr->funcid, GetUserId(), ACL_EXECUTE);
	if (fc_aclresult != ACLCHECK_OK)
		aclcheck_error(fc_aclresult, OBJECT_PROCEDURE, get_func_name(fc_fexpr->funcid));

	/* 准备我们将传递给过程的上下文对象 */
	fc_callcontext = makeNode(CallContext);
	fc_callcontext->atomic = fc_atomic;

	fc_tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_fexpr->funcid));
	if (!HeapTupleIsValid(fc_tp))
		elog(ERROR, "cache lookup failed for function %u", fc_fexpr->funcid);

	/*
	 * 如果 proconfig 被设置，我们不能允许事务命令，
	 * 因为 GUC 堆栈的工作方式：事务边界必须将
	 * proconfig 设置从堆栈中弹出。通过稍微重新设计 GUC 嵌套机制
	 * 可以解除该限制。
	 */
	if (!heap_attisnull(fc_tp, Anum_pg_proc_proconfig, NULL))
		fc_callcontext->atomic = true;

	/*
	 * 在安全定义程序中，我们不能允许事务命令。
	 * StartTransaction() 坚持要求安全上下文栈为空，
	 * 而 AbortTransaction() 重置安全上下文。这可以
	 * 重新组织，但目前不适用。
	 */
	if (((Form_pg_proc) GETSTRUCT(fc_tp))->prosecdef)
		fc_callcontext->atomic = true;

	ReleaseSysCache(fc_tp);

	/* 安全检查；见 ExecInitFunc() */
	fc_nargs = list_length(fc_fexpr->args);
	if (fc_nargs > FUNC_MAX_ARGS)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
				 errmsg_plural("cannot pass more than %d argument to a procedure",
							   "cannot pass more than %d arguments to a procedure",
							   FUNC_MAX_ARGS,
							   FUNC_MAX_ARGS)));

	/* 初始化函数调用结构 */
	InvokeFunctionExecuteHook(fc_fexpr->funcid);
	fmgr_info(fc_fexpr->funcid, &fc_flinfo);
	fmgr_info_set_expr((Node *) fc_fexpr, &fc_flinfo);
	InitFunctionCallInfoData(*fc_fcinfo, &fc_flinfo, fc_nargs, fc_fexpr->inputcollid,
							 (Node *) fc_callcontext, NULL);

	/*
	 * 在合适的执行上下文中评估过程参数。注意，
	 * 在过程返回之前我们不能释放此上下文。
	 */
	fc_estate = CreateExecutorState();
	fc_estate->es_param_list_info = fc_params;
	fc_econtext = CreateExprContext(fc_estate);

	/*
	 * 如果我们在非原子上下文中被调用，我们还必须确保
	 * 参数表达式使用最新的快照运行。我们的调用者将在原子上下文中
	 * 提供当前快照，但在非原子上下文中不会提供，因为
	 * COMMIT/ROLLBACK 破坏快照的可能性使得更高级别的管理过于复杂。
	 */
	if (!fc_atomic)
		PushActiveSnapshot(GetTransactionSnapshot());

	fc_i = 0;
	foreach(fc_lc, fc_fexpr->args)
	{
		ExprState  *fc_exprstate;
		Datum		fc_val;
		bool		fc_isnull;

		fc_exprstate = ExecPrepareExpr(lfirst(fc_lc), fc_estate);

		fc_val = ExecEvalExprSwitchContext(fc_exprstate, fc_econtext, &fc_isnull);

		fc_fcinfo->args[fc_i].value = fc_val;
		fc_fcinfo->args[fc_i].isnull = fc_isnull;

		fc_i++;
	}

	/* 如果我们创建了临时快照，则去掉它 */
	if (!fc_atomic)
		PopActiveSnapshot();

	/* 在这里我们实际上调用过程 */
	pgstat_init_function_usage(fc_fcinfo, &fc_fcusage);
	fc_retval = FunctionCallInvoke(fc_fcinfo);
	pgstat_end_function_usage(&fc_fcusage, true);

	/* 处理过程的输出 */
	if (fc_fexpr->funcresulttype == VOIDOID)
	{
		/* 不执行任何操作 */
	}
	else if (fc_fexpr->funcresulttype == RECORDOID)
	{
		/* 将元组发送给客户端 */
		HeapTupleHeader fc_td;
		Oid			fc_tupType;
		int32		fc_tupTypmod;
		TupleDesc	fc_retdesc;
		HeapTupleData fc_rettupdata;
		TupOutputState *fc_tstate;
		TupleTableSlot *fc_slot;

		if (fc_fcinfo->isnull)
			elog(ERROR, "procedure returned null record");

		/*
		 * 确保在执行这里涉及的任何操作时有一个活动的快照。
		 * 注意，这 *不* 足以使 TOAST 指针包含在返回的数据中：
		 * 引用的数据可能在我们未持有快照的情况下消失。
		 * 因此，能够进行 COMMIT/ROLLBACK 的 PL 需
		 * 要确保不返回 TOAST 指针，除非这些指针是在
		 * 过程最后一次 COMMIT/ROLLBACK 之后获取的。
		 *
		 * XXX 这是一个非常棘手、难以测试的要求。是否有
		 * 方式移除它？
		 */
		EnsurePortalSnapshotExists();

		fc_td = DatumGetHeapTupleHeader(fc_retval);
		fc_tupType = HeapTupleHeaderGetTypeId(fc_td);
		fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_td);
		fc_retdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);

		fc_tstate = begin_tup_output_tupdesc(fc_dest, fc_retdesc,
										  &TTSOpsHeapTuple);

		fc_rettupdata.t_len = HeapTupleHeaderGetDatumLength(fc_td);
		ItemPointerSetInvalid(&(fc_rettupdata.t_self));
		fc_rettupdata.t_tableOid = InvalidOid;
		fc_rettupdata.t_data = fc_td;

		fc_slot = ExecStoreHeapTuple(&fc_rettupdata, fc_tstate->slot, false);
		fc_tstate->dest->receiveSlot(fc_slot, fc_tstate->dest);

		end_tup_output(fc_tstate);

		ReleaseTupleDesc(fc_retdesc);
	}
	else
		elog(ERROR, "unexpected result type for procedure: %u",
			 fc_fexpr->funcresulttype);

	FreeExecutorState(fc_estate);
}

/*
 * 为 CALL 语句返回构建元组描述符
 */
TupleDesc CallStmtResultDesc(CallStmt *fc_stmt)
{
	FuncExpr   *fc_fexpr;
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupdesc;

	fc_fexpr = fc_stmt->funcexpr;

	fc_tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fc_fexpr->funcid));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for procedure %u", fc_fexpr->funcid);

	fc_tupdesc = build_function_result_tupdesc_t(fc_tuple);

	ReleaseSysCache(fc_tuple);

	/*
	 * build_function_result_tupdesc_t 的结果具有正确的列
	 * 名，但它仅具有声明的输出参数类型，在多态情况下这是
	 * 错误的。通过检查 stmt->outargs 获取正确的类型。
	 * 我们故意保持 atttypmod 为 -1，attcollation 为类型的默认值，
	 * 因为这对于函数输出始终是适当的；考虑 outargs 中的
	 * 任何附加信息都是没有意义的。请注意，如果没有 outargs，tupdesc 为 null。
	 */
	if (fc_tupdesc)
	{
		Assert(fc_tupdesc->natts == list_length(fc_stmt->outargs));
		for (int fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
			Node	   *fc_outarg = (Node *) list_nth(fc_stmt->outargs, fc_i);

			TupleDescInitEntry(fc_tupdesc,
							   fc_i + 1,
							   NameStr(fc_att->attname),
							   exprType(fc_outarg),
							   -1,
							   0);
		}
	}

	return fc_tupdesc;
}
